package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointOutside;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.RoundRectangle2D;
import java.util.HashMap;
import java.util.Map;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.RectangleShape;
import xj.graph2d.Rotatable;
import xj.graph2d.graph.Geometry;

import static xj.graph2d.graph.Geometry.CHECK;

public class RoundRectangleShape 
  extends RectangleShape 
  implements Rotatable {

  public RoundRectangleShape() {
  }

  public RoundRectangleShape(boolean filled) {
    super(filled);
  }

  public RoundRectangleShape(boolean filled, boolean createComp) {
    super(filled, createComp);
  }

  public RoundRectangleShape(float arcw, float arch, boolean filled) {
    super(filled);
    arcWidth = arcw;
    arcHeight = arch;
  }

  public RoundRectangleShape(float arcw, float arch, 
			     boolean filled, boolean createComp) {
    super(filled, createComp);
    arcWidth = arcw;
    arcHeight = arch;
  }

  public RoundRectangleShape(float x1, float y1, float x2, float y2) {
    super(x1, y1, x2, y2);
  }

  public RoundRectangleShape(float x1, float y1, float x2, float y2,
			     boolean filled) {
    super(x1, y1, x2, y2, filled);
  }

  public RoundRectangleShape(float x1, float y1, float x2, float y2,
			     boolean filled, boolean createComp) {
    super(x1, y1, x2, y2, filled, createComp);
  }

  public RoundRectangleShape(float x1, float y1, float x2, float y2,
			     float arcw, float arch) {
    this(x1, y1, x2, y2, arcw, arch, false);
  }

  public RoundRectangleShape(float x1, float y1, float x2, float y2,
			     float arcw, float arch, boolean filled) {
    super(x1, y1, x2, y2, filled);
    arcWidth = arcw;
    arcHeight = arch;
  }

  public RoundRectangleShape(float x1, float y1, float x2, float y2,
			     float arcw, float arch, 
			     boolean filled, boolean createComp) {
    super(x1, y1, x2, y2, filled, createComp);
    arcWidth = arcw;
    arcHeight = arch;
  }

  @Override
  public DrawShape makeInstance() {
    return new RoundRectangleShape(filled, fullCloneMode && comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    if (s != null) {
      super.copyTo(s);
      if (s instanceof RoundRectangleShape) {
	RoundRectangleShape r = (RoundRectangleShape) s;
	r.arcWidth = arcWidth;
	r.arcHeight = arcHeight;
	r.round = round;
      }
    }
  }

  @Override
  public String getName() {
    return "Round rectangle";
  }

  public boolean isRound() { 
    return round; 
  }

  public void setRound(boolean round) { 
    this.round = round; 
  }

  public float getArcWidth() {
    return arcWidth;
  }

  public float getArcHeight() {
    return arcHeight;
  }

  public void setArcWidth(float arcw) {
    arcWidth = arcw;
  }

  public void setArcHeight(float arch) {
    arcHeight = arch;
  }

  public void setArc(float arcw, float arch) {
    arcWidth = arcw;
    arcHeight = arch;
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    setEndsAdj(x1, y1, x2, y2, true);
  }

  public void setEndsAdj(float x1, float y1, float x2, float y2,
			 boolean scaleArc) {
    if (debug) {
      System.out.println("RoundRectangleShape.setEndsAdj()");
    }

    float xmin = (float) Math.min(p1.getX(), p2.getX());
    float ymin = (float) Math.min(p1.getY(), p2.getY());
    float xmax = (float) Math.max(p1.getX(), p2.getX());
    float ymax = (float) Math.max(p1.getY(), p2.getY());

    float sx = 1;
    float sy = 1;
    if (x1 == x2 && y1 == y2) {
      sx = 0;
      sy = 0;
    } else {
      if (xmax != xmin) {
	sx = Math.abs(x2 - x1) / (xmax - xmin);
      }
      if (ymax != ymin) {
	sy = Math.abs(y2 - y1) / (ymax - ymin);
      }
    }

    super.setEndsAdj(x1, y1, x2, y2);

    if (scaleArc) {
      arcWidth *= sx;
      arcHeight *= sy;

      if (arcWidth < MINIMUM_ARC_WIDTH) {
	arcWidth = MINIMUM_ARC_WIDTH;
      }
      if (arcHeight < MINIMUM_ARC_HEIGHT) {
	arcHeight = MINIMUM_ARC_HEIGHT;
      }
    }
  }

  @Override
  public void setEndsInit(float x1, float y1, float x2, float y2) {
    setEndsAdj(x1, y1, x2, y2, false);
  }

  @Override
  protected void scaleEx(float sx, float sy) {
    // arcWidth *= sx;
    // arcHeight *= sy;
  }

  @Override
  public void draw(Graphics2D g2) {
    if (round) { 
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      Shape shape = new RoundRectangle2D.Float(x, y, w, h, arcWidth, arcHeight);
      if (filled) {
	fillBackground(g2, x, y, w, h, shape);
	if (showborder) {
	  g2.draw(shape);
	}
      } else {
	g2.draw(shape);
      }
      drawGutter(g2, x, y, w, h);
    } else { 
      super.draw(g2);
    }
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    if (round) { 
      DrawAttr attr = getDrawAttr();
      if (attr != null) {
	g2.setColor(attr.getPenColor());
      } else {
	g2.setColor(DrawAttr.getDefaultPenColor());
      }
      float x = Math.min(x1, x2);
      float y = Math.min(y1, y2);
      float w = Math.abs(x1 - x2);
      float h = Math.abs(y1 - y2);
      g2.draw(new RoundRectangle2D.Float(x, y, w, h, arcWidth, arcHeight));
    } else { 
      super.drawOutline(g2, x1, y1, x2, y2);
    }
  }

  @Override
  public Shape getOutlineShape() {
    if (round) { 
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      return new RoundRectangle2D.Float(x, y, w, h, arcWidth, arcHeight);
    } else {
      return super.getOutlineShape();
    }
  }

  /* return the intersection point of p and the center of the node */
  @Override
  public Point2D intersect(Point2D p, float offsetX, float offsetY) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float cx = x + w / 2 + offsetX;
      float cy = y + h / 2 + offsetY;
      Point2D p1 = Geometry.intersectionRectLine(x, y, x + w, y + h, px, py, cx, cy);

      if (p1 == null) { 
	return null;
      }

      float x1 = (float) p1.getX();
      float y1 = (float) p1.getY();
      float arcW = arcWidth / 2;
      float arcH = arcHeight / 2;

      if (isInside(p) != PointOutside) {
	float t;
	t = px;
	px = cx;
	cx = t;
	t = py;
	py = cy;
	cy = t;
      }

      //CHECK(p1);
      if (x1 >= (x + arcW) && x1 <= (x + w - arcW) || 
	  y1 >= (y + arcH) && y1 <= (y + h - arcH)) {
	return p1;
      } else {
	Point2D p2 = null;
	if (x1 < (x + arcW)) {
	  if (y1 < (y + arcH)) {
	    p2 = Geometry.intersectionEllipseLine(x, y, x + 2 * arcW, y + 2 * arcH, 
						  px, py, cx, cy);
	  } else if (y1 > (y + h - arcH)) {
	    p2 = Geometry.intersectionEllipseLine(x, y + h - 2 * arcH, x + 2 * arcW, 
						  y + h, px, py, cx, cy);
	  }
	} else if (x1 > (x + w - arcW)) {
	  if (y1 < (y + arcH)) {
	    p2 = Geometry.intersectionEllipseLine(x + w - 2 * arcW, y, x + w, y + 2 * arcH, 
						  px, py, cx, cy);
	  } else if (y1 > (y + h - arcH)) {
	    p2 = Geometry.intersectionEllipseLine(x + w - 2 * arcW, y + h - 2 * arcH, x + w, y + h, 
						  px, py, cx, cy);
	  }
	}
	if (p2 != null) {
	  //CHECK(p2);
	  return p2;
	}
      }
      return p1;
    } else {
      return null;
    }
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("arch", arcHeight);
    attrs.put("arcw", arcWidth);
    if (!round) { 
      attrs.put("round", round);
    }
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("arch");
      try {
	arcHeight = Float.parseFloat(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("arcw");
      try {
	arcWidth = Float.parseFloat(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("round");
      if (s != null && s.length() > 0) {
	round = Boolean.valueOf(s);
      } else {
	round = true;
      }
    }
  }

  public static final float DEFAULT_ARC_HEIGHT = 10f;

  public static final float DEFAULT_ARC_WIDTH = 10f;

  public static final float MINIMUM_ARC_HEIGHT = 4f;

  public static final float MINIMUM_ARC_WIDTH = 4f;

  protected boolean round = true; 

  protected float arcHeight = DEFAULT_ARC_HEIGHT;

  protected float arcWidth = DEFAULT_ARC_WIDTH;

  protected static final boolean debug = false;

}
